package com.chang.cart.service.impl;


import cn.hutool.json.JSONUtil;
import com.chang.cart.feign.ProductFeignService;
import com.chang.cart.interceptor.CartInterceptor;
import com.chang.cart.service.CartService;
import com.chang.cart.utils.RedisUtils;
import com.chang.cart.vo.CartItemVo;
import com.chang.cart.vo.CartVo;
import com.chang.cart.vo.SkuInfoVo;
import com.chang.cart.vo.UserInfoTo;
import com.chang.common.utils.R;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author: changzhenxi
 * @date: 2024年12月16日 14:59
 * @Version: 1.0
 * @description: 这里的数据都要存放到redis中 数据结构为 整个购物车用的是CART_PREFIX+用户标识作为key
 * 购物车中的购物项用的是商品id和购物项详细内容
 */
@Service
@Slf4j
public class CartServiceImpl implements CartService {

    @Resource
    ProductFeignService productFeignService;

    @Resource
    ThreadPoolExecutor executor;

    private final String CART_PREFIX = "gulimall:cart:";


    // 这里的多次远程调用可以用多线程做个异步处理
    @Override
    public CartItemVo addToCart(Long skuId, Integer num) {
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        // 如果商品不存在然后再添加
        if (!RedisUtils.exists(skuId.toString())) {
            // 添加新商品到购物车
            CartItemVo cartItem = new CartItemVo();
            // runAsync不关心返回值
            CompletableFuture<Void> getSkuInfoTask = CompletableFuture.runAsync(() -> {
                // 远程查询商品信息
                R info = productFeignService.info(skuId);
                SkuInfoVo skuInfo = JSONUtil.toBean(info.getData(), SkuInfoVo.class);
                cartItem.setCheck(true);
                cartItem.setCount(num); // 商品数量
                cartItem.setImage(skuInfo.getSkuDefaultImg());
                cartItem.setTitle(skuInfo.getSkuTitle());
                cartItem.setSkuId(skuId);
                cartItem.setPrice(skuInfo.getPrice());
            }, executor);
            //
            CompletableFuture<Void> getSkuAttrListTask = CompletableFuture.runAsync(() -> {
                // 远程查询sku的销售属性组合信息
                List<String> skuSaleAttrValueList = productFeignService.getSkuSaleAttrValueListBySkuId(skuId);
                cartItem.setSkuAttrValues(skuSaleAttrValueList);
            }, executor);

            // 使用get阻塞式等待两个异步都执行完
            try {
                CompletableFuture.allOf(getSkuInfoTask, getSkuAttrListTask).get();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } catch (ExecutionException e) {
                throw new RuntimeException(e);
            }
            // 注意redis不能直接存放对象,因为redis需要先把对象序列化转成字符流,而字符流默认使用jdk的序列化方式,
            String cartItemString = JSONUtil.toJsonStr(cartItem);
            cartOps.put(skuId.toString(), cartItemString);
            return cartItem;
        } else { // 购物车如果有这件商品那就只需要修改数据即可
            String object = (String) cartOps.get(CART_PREFIX + "skuId");
            CartItemVo cartItem = JSONUtil.toBean(object, CartItemVo.class);
            cartItem.setCount(cartItem.getCount() + num);
            cartOps.put(skuId.toString(), JSONUtil.toJsonStr(cartItem));
            return cartItem;
        }
    }


    // 获取整个购物车的信息 这里需要先区分一下是登录状态还是未登录状态
    @Override
    public CartVo getCart() {
        UserInfoTo userInfoTo = CartInterceptor.memberThreadLocal.get();
        // redis填装对象
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();

        CartVo cart = new CartVo();

        // 获取临时购物车的数据
        List<CartItemVo> tempCartItems = getCartItems(userInfoTo.getUserKey());

        log.info("tempCartItems=>{}", tempCartItems);
        if (userInfoTo.getUserId() != null) {
            List<CartItemVo> cartItems = getCartItems(userInfoTo.getUserId().toString());
            log.info("cartItems=>{}", cartItems);
            // 如果用户购物车有数据
            if (cartItems != null && !cartItems.isEmpty()) {
                // 如果临时购物车不为空,登录之后的购物车也不为空则需要合并两个购物车  这里老师说的合并指的是继续重复调用上面的addToCart(),但是里面有两次的远程调用方法,浪费资源
                if (tempCartItems != null && !tempCartItems.isEmpty()) {
                    for (CartItemVo cartItem : cartItems) {
                        for (CartItemVo tempCartItem : tempCartItems) {
                            // 如果临时购物车的商品id和商品销售属性全都相同,则数量相加
                            if (cartItem.getSkuId().equals(tempCartItem.getSkuId()) && cartItem.getSkuAttrValues().equals(tempCartItem.getSkuAttrValues())) {
                                cartItem.setCount(cartItem.getCount() + tempCartItem.getCount());
                                // 然后剔除相同的商品
                                tempCartItems.remove(tempCartItem);
                            }
                        }
                    }
                    // 剔除掉相同的商品之后,将临时购物车剩下不同的产品直接放一块
                    tempCartItems.forEach(cartItems::add);
                    // 删除redis中的临时购物车数据
                    deleteRedisCartItem(userInfoTo.getUserKey());
                }
                //用户登录且购物车有数据，但是临时购物车没数据则只返回用户购物车的数据 并存到redis中
                // 获取购物车时应该远程调用获取最新的商品价格
                List<CartItemVo> cartItemVos = updatePrice(cartItems);
                addCartToRedis(cartItemVos);
                cart.setItems(cartItemVos);
            } else {
                if (tempCartItems != null && !tempCartItems.isEmpty()) {
                    // 用户登录但用户购物车没数据，则依旧返回临时购物车数据 存到redis中后清空临时购物车
                    List<CartItemVo> cartItemVos = updatePrice(tempCartItems);
                    addCartToRedis(cartItemVos);
                    cart.setItems(cartItemVos);
                    deleteRedisCartItem(userInfoTo.getUserKey());
                }
            }
        } else {
            // 如果没有用户登录就只返回临时购物车
            cart.setItems(tempCartItems);
        }
        return cart;
    }

    @Override
    public void checkItem(Long skuId, Integer checked) {
        CartItemVo cartItem = getCartItem(skuId);
        cartItem.setCheck(checked == 1);
        String jsonStr = JSONUtil.toJsonStr(cartItem);
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        cartOps.put(skuId.toString(), jsonStr);
    }

    @Override
    public void changeItemCount(Long skuId, Integer num) {
        CartItemVo cartItem = getCartItem(skuId);
        cartItem.setCount(num);
        getCartOps().put(skuId.toString(), JSONUtil.toJsonStr(cartItem));
    }

    @Override
    public void deleteItem(Long skuId) {
        getCartOps().delete(skuId.toString());
    }


    // 获取所有购物项
    private List<CartItemVo> getCartItems(String Key) {
        String userKey = CART_PREFIX + Key;
        BoundHashOperations<String, Object, Object> operations = RedisUtils.boundHash(userKey);
        List<Object> values = operations.values();
        log.info("values=>{}", values);

        if (values != null && !values.isEmpty()) {
            return values.stream().map((item) -> {
                        String jsonStr = JSONUtil.toJsonStr(item);
                        CartItemVo cartItemVo = JSONUtil.toBean(jsonStr, CartItemVo.class);
                        log.info("属性拷贝结果cartItemVo=>{}", cartItemVo);
                        return cartItemVo;
                    }
            ).toList();
        }
        return null;
    }

    // 获取购物车中的某个购物项
    @Override
    public CartItemVo getCartItem(Long skuId) {
        BoundHashOperations<String, Object, Object> cartOps = getCartOps();
        String cartString = (String) cartOps.get(skuId.toString());
        return JSONUtil.toBean(cartString, CartItemVo.class);
    }

    // 购物车内容全部存放在redis中，这里专门提取方法用于获取redis中的购物车信息
    private BoundHashOperations<String, Object, Object> getCartOps() {
        // 首先判断是登录用户还是临时用户
        UserInfoTo userInfo = CartInterceptor.memberThreadLocal.get();
        String cartKey;
        if (userInfo.getUserId() != null) {
            cartKey = CART_PREFIX + userInfo.getUserId();
        } else {
            cartKey = CART_PREFIX + userInfo.getUserKey();
        }
        return RedisUtils.boundHash(cartKey);
    }

    // 清空redis中的购物车缓存
    private void deleteRedisCartItem(String key) {
        RedisUtils.del(CART_PREFIX + key);
    }

    // 向redis中添加购物车缓存
    private void addCartToRedis(List<CartItemVo> cartItems) {
        if (cartItems != null && !cartItems.isEmpty()) {
            BoundHashOperations<String, Object, Object> cartOps = getCartOps();
            cartItems.forEach(cartItem -> {
                cartOps.put(cartItem.getSkuId().toString(), JSONUtil.toJsonStr(cartItem));
            });
        }
    }


    @Override
    public List<CartItemVo> getUserCartItems() {
        UserInfoTo userInfoTo = CartInterceptor.memberThreadLocal.get();
        log.info("本地线程获取用户信息:userInfoTo=>{}", userInfoTo);
        if (userInfoTo.getUserId() != null) {
            // getCartItems 是从redis中获取的购物车数据，这里的价格更新我直接在组合购物车的时候直接更新了
            List<CartItemVo> cartItems = getCartItems(userInfoTo.getUserId().toString());
            if (cartItems != null) {
                // 过滤掉未选中的购物项
                List<CartItemVo> list = cartItems.stream().filter(CartItemVo::getCheck).toList();
                return list;
            } else return null;
        } else return null;

    }

    public List<CartItemVo> updatePrice(List<CartItemVo> cartItems) {
        // 商品id集合 用于远程调用更新商品价格
        List<Long> skuIds = new ArrayList<>();
        cartItems.forEach(cartItem -> {
            skuIds.add(cartItem.getSkuId());
        });
        Map<Long, String> priceMap = productFeignService.getNewPrice(skuIds);
        cartItems.forEach(item -> {
            item.setPrice(new BigDecimal(priceMap.get(item.getSkuId())));
        });
        return cartItems;
    }
}
